package com.yy.wga.user.service.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.yy.wga.common.core.dto.Result;
import com.yy.wga.common.core.dto.UserCacheDTO;
import com.yy.wga.common.core.enmus.EProfilesType;
import com.yy.wga.common.core.enmus.EResponseCode;
import com.yy.wga.common.core.enmus.EUserGrade;
import com.yy.wga.common.core.exception.BusinessException;
import com.yy.wga.common.util.aop.DistributedLock;
import com.yy.wga.common.util.common.IdUtils;
import com.yy.wga.common.util.common.JsonUtils;
import com.yy.wga.common.util.common.SpringUtils;
import com.yy.wga.common.util.common.StringUtils;
import com.yy.wga.user.feign.request.*;
import com.yy.wga.user.feign.response.AliH5PayLoginResp;
import com.yy.wga.user.feign.response.QueryAccountResp;
import com.yy.wga.user.feign.response.WxAuthCode2SessionResp;
import com.yy.wga.user.feign.response.WxGetUserInfoResp;
import com.yy.wga.user.service.constant.RedisConstant;
import com.yy.wga.user.service.mapper.UserBaseMapper;
import com.yy.wga.user.service.pojo.UserBaseDO;
import com.yy.wga.user.service.pojo.UserRelationDO;
import com.yy.wga.user.service.pojo.UserWechatDO;
import com.yy.wga.user.service.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * description
 *
 * @author <a href="mailto:ludezh@dingtalk.com"> ludezh </a>
 * @version 1.0.0   2020/6/5,11:38
 * @since 1.0.0     2020/6/5,11:38
 */
@Slf4j
@Service
public class UserManager extends ServiceImpl<UserBaseMapper, UserBaseDO> {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private UserBaseService userBaseService;
    @Resource
    private UserLoginLogService loginLogService;
    @Resource
    private UserWechatService wechatService;
    @Resource
    private IncomeUserBalanceService incomeUserBalanceService;
    @Resource
    private SmsManager smsManagerService;
    @Resource
    private UserGradeRecordService userGradeRecordService;
    @Resource
    private UserRelationService userRelationService;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Resource
    private IPromoterService promoterService;
    @Resource
    private WechatMpFeign wechatMpFeign;

    @Value(value = "${spring.profiles.active:}")
    private String profiles;
    @Resource
    private UserBaseMapper userBaseMapper;


    /**
     * 小程序登录
     *
     * @param request 微信登录授权信息
     * @return com.hrz.common.base.Result<com.hrz.hrzb.entity.response.WxAuthCode2SessionResponse>
     * @author ludezh
     * @date 2020/6/6 12:54
     * @version 1.0.0
     */
    public WxAuthCode2SessionResp authCode2Session(WxAuthCode2SessionReq request) {
        Result<WxMaJscode2SessionResult> userSessionInfo = wechatMpFeign.getUserSessionInfo(request.getCode());
        if (!userSessionInfo.isSuccess()) {
            throw new BusinessException(userSessionInfo.getMessage());
        }
        WxMaJscode2SessionResult session = userSessionInfo.getData();
        if (StringUtils.isNotBlank(session.getSessionKey())) {
            // 注册用户信息，已注册用户则返回手机号
            WxAuthCode2SessionResp response = saveWechatInfo(session, request);

            // 修改缓存
            UserCacheDTO updateUserCache = BeansUtils.map(response, UserCacheDTO.class);
            updateUserCache.setToken(IdUtils.uuid());
            updateUserCache.setPhone(response.getPhoneNumber());
            updateUserCache.setUnionId(session.getUnionid());
            updateUserCache.setOpenId(session.getOpenid());
            updateUserCache.setSessionKey(session.getSessionKey());
            UserManager bean = SpringUtils.getBean(this.getClass());
            bean.addUserCache(updateUserCache);

            // 返回登录响应实体
            response.setAccountSecToken(updateUserCache.getToken());
            response.setTokenExpire(RedisConstant.ACCOUNT_TOKEN_EXPIRE_MINUTES);
            response.setOpenId(session.getOpenid());
            response.setUnionId(session.getUnionid());
            response.setSecKey(session.getSessionKey());
            return response;
        }
        throw new BusinessException(EResponseCode.FAIL);
    }

    /**
     * 微信公众号授权
     *
     * @param request
     * @return
     */
    public WxAuthCode2SessionResp officialAccountAuthCode2Session(WxAuthCode2SessionRequest request) {
        Result<WxOAuth2AccessToken> oAuth2AccessTokenResult = wechatMpFeign.getOfficialOAuth2AccessToken(request.getCode());
        if (!oAuth2AccessTokenResult.isSuccess()) {
            throw new BusinessException(oAuth2AccessTokenResult.getMessage());
        }

        WxOAuth2AccessToken accessToken = oAuth2AccessTokenResult.getData();
        UserWechatDO wechat = wechatService.getByUnionId(RequestUserHolder.getAppKey(), accessToken.getUnionId());
        if (wechat == null) {
            throw new BusinessException("用户不存在");
        }
        UserBaseDO userBaseDO = userBaseService.getOne(new LambdaQueryWrapper<UserBaseDO>().eq(UserBaseDO::getAppKey, RequestUserHolder.getAppKey()).eq(UserBaseDO::getId, wechat.getUserId()));
        // 注册用户信息，已注册用户则返回手机号
        WxAuthCode2SessionResp response = new WxAuthCode2SessionResp();
        response.setCountry(wechat.getCountry());
        response.setProvince(wechat.getProvince());
        response.setCity(wechat.getCity());
        response.setUserId(userBaseDO.getId());
        response.setAvatarUrl(userBaseDO.getAvatarUrl());
        response.setSex(userBaseDO.getSex());
        response.setNickName(userBaseDO.getNickName());
        response.setPhoneNumber(userBaseDO.getPhone());
        response.setGrade(userBaseDO.getGrade());
        response.setLevel(userBaseDO.getLevel());
        response.setInviteCode(userBaseDO.getInviteCode());
        response.setWxNum(userBaseDO.getWxNum());
        response.setRegisterBindStatus(userBaseDO.getRegisterBindStatus());

        // 修改缓存
        UserCacheDTO updateUserCache = BeansUtils.map(response, UserCacheDTO.class);
        updateUserCache.setToken(IdUtils.uuid());
        updateUserCache.setPhone(response.getPhoneNumber());
        updateUserCache.setUnionId(accessToken.getUnionId());
        updateUserCache.setOpenId(accessToken.getOpenId());
        updateUserCache.setSessionKey(IdUtils.uuid());
        UserManager bean = SpringUtils.getBean(this.getClass());
        bean.addUserCache(updateUserCache);

        // 返回登录响应实体
        response.setAccountSecToken(updateUserCache.getToken());
        response.setTokenExpire(RedisConstant.ACCOUNT_TOKEN_EXPIRE_MINUTES);
        response.setOpenId(accessToken.getOpenId());
        response.setUnionId(accessToken.getUnionId());
        response.setSecKey(updateUserCache.getSessionKey());
        return response;
    }

    /**
     * 小程序用户信息授权
     *
     * @param request 用户微信信息
     * @return 响应接口
     */
    @Transactional(rollbackFor = Exception.class)
    public WxGetUserInfoResp getWxUserInfo(WxGetUserInfoReq request) {
        if (StringUtils.isNotBlank(request.getEncryptedData()) && StringUtils.isNotBlank(request.getIv())) {

            UserCacheDTO accountDTO = RequestUserHolder.getUserCache();
            WxGetUserInfoRequest getUserInfoRequest = new WxGetUserInfoRequest();
            getUserInfoRequest.setIv(request.getIv());
            getUserInfoRequest.setEncryptedData(request.getEncryptedData());
            WxGetUserInfoResponse response = WxCryptUtil.cryptXcxUserInfo(getUserInfoRequest, accountDTO.getSessionKey());

            // 填充响应信息
            response.setUserId(accountDTO.getUserId());
            response.setPhoneNumber(accountDTO.getPhone());
            response.setGrade(accountDTO.getGrade());
            response.setLevel(accountDTO.getLevel());
            response.setInviteCode(accountDTO.getInviteCode());
            response.setOpenId(accountDTO.getOpenId());
            response.setUnionId(accountDTO.getUnionId());

            // 微信信息持久化
            this.saveWechatInfoToDb(response);

            // 更新缓存
            UserCacheDTO updateUserCache = new UserCacheDTO();
            updateUserCache.setGender(response.getGender());
            updateUserCache.setAvatarUrl(response.getAvatarUrl());
            updateUserCache.setUserName(response.getNickName());
            updateUserCache.setUserId(response.getUserId());
            updateUserCache.setPhone(response.getPhoneNumber());
            updateUserCache.setGrade(response.getGrade());
            updateUserCache.setLevel(response.getLevel());
            UserManager bean = SpringUtils.getBean(this.getClass());
            bean.setUserCache(updateUserCache);
            return response;
        }
        throw new BusinessException(EResponseCode.PARAM_ERROR_CODE);
    }

    /**
     * 保存微信信息到数据库
     *
     * @param response 响应实体
     */
    public void saveWechatInfoToDb(WxGetUserInfoResponse response) {
        // 更新数据库微信信息
        wechatService.update(new LambdaUpdateWrapper<UserWechatDO>().eq(UserWechatDO::getAppKey, RequestUserHolder.getBaseRequest().getAppKey()).eq(UserWechatDO::getOpenId, response.getOpenId()).set(UserWechatDO::getAvatarUrl, response.getAvatarUrl()).set(UserWechatDO::getCity, response.getCity()).set(UserWechatDO::getCountry, response.getCountry()).set(UserWechatDO::getProvince, response.getProvince()).set(UserWechatDO::getGender, response.getGender()).set(UserWechatDO::getLanguage, response.getLanguage()).set(UserWechatDO::getWxName, response.getNickName()));


        // 修改account_base用户信息
        userBaseService.update(new LambdaUpdateWrapper<UserBaseDO>().eq(UserBaseDO::getAppKey, RequestUserHolder.getBaseRequest().getAppKey()).eq(UserBaseDO::getId, response.getUserId()).set(UserBaseDO::getAvatarUrl, response.getAvatarUrl()).set(UserBaseDO::getNickName, response.getNickName()).set(UserBaseDO::getSex, response.getGender()));
    }

    /**
     * 绑定手机号
     *
     * @param request 手机号加密信息
     * @return com.hrz.common.base.Result<com.hrz.hrzb.entity.response.WxGetPhoneNumberResponse>
     * @author ludezh
     * @date 2020/6/6 12:56
     * @version 1.0.0
     */
    @Transactional(rollbackFor = Exception.class)
    public WxGetPhoneNumberResponse authPhoneNumber(WxGetPhoneNumberRequest request) {
        UserCacheDTO userCache = RequestUserHolder.getUserCache();
        WxGetPhoneNumberDTO dto = WxCryptUtil.cryptXcxPhoneNum(request, userCache.getSessionKey());
        if (null != dto && StringUtils.isNotBlank(dto.getPurePhoneNumber())) {
            // 保存用户信息
            WxGetPhoneNumberResponse response = this.saveUserInfo(dto, request);

            // 修改缓存
            UserCacheDTO updateUserCache = new UserCacheDTO();
            updateUserCache.setUserId(RequestUserHolder.getUserCache().getUserId());
            updateUserCache.setPhone(dto.getPurePhoneNumber());
            UserManager bean = SpringUtils.getBean(this.getClass());
            bean.setUserCache(updateUserCache);
            return response;
        }
        throw new BusinessException(EResponseCode.PARAM_ERROR_CODE);
    }

    /**
     * 保存微信信息
     *
     * @param dto     微信信息
     * @param request 请求参数
     * @return 是有有微信信息
     */
    private WxAuthCode2SessionResp saveWechatInfo(WxMaJscode2SessionResult dto, WxAuthCode2SessionRequest request) {
        UserBaseDO userBaseDO;
        // 用户第一次登录，创建用户，否则获取用户信息
        UserWechatDO wechat = wechatService.getByOpenId(RequestUserHolder.getAppKey(), dto.getOpenid());
        if (wechat == null) {
            Integer userGrade = EUserGrade.SHARE_1.getType();
            int userLevel = 1;
            // 保存用户信息
            userBaseDO = new UserBaseDO();
            userBaseDO.setAppKey(RequestUserHolder.getBaseRequest().getAppKey());
            userBaseDO.setSex(0);
            userBaseDO.setNickName("");
            userBaseDO.setEmail("");
            userBaseDO.setAvatarUrl("");
            userBaseDO.setGrade(userGrade);
            userBaseDO.setLevel(userLevel);
            userBaseService.save(userBaseDO);

            // 保存微信信息
            wechat = new UserWechatDO();
            wechat.setAppKey(RequestUserHolder.getBaseRequest().getAppKey());
            wechat.setUserId(userBaseDO.getId());
            wechat.setOpenId(dto.getOpenid());
            wechat.setUnionId(dto.getUnionid());
            wechat.setGender(0);
            wechat.setWxName("");
            wechat.setAvatarUrl("");
            wechat.setCountry("");
            wechat.setCity("");
            wechat.setProvince("");
            wechat.setLanguage("");
            wechat.setPhone("");
            wechatService.save(wechat);
        } else {
            // 获取用户信息
            userBaseDO = userBaseService.getOne(new LambdaQueryWrapper<UserBaseDO>().eq(UserBaseDO::getId, wechat.getUserId()));

            if (userBaseDO.getStatus() != 1) {
                throw new BusinessException("用户已禁用或注销，请联系管理员");
            }
        }
        // 异步保存登录日志
        if (StringUtils.isNotBlank(userBaseDO.getPhone())) {
            UserLoginLogDO loginLog = new UserLoginLogDO();
            loginLog.setAppKey(RequestUserHolder.getBaseRequest().getAppKey());
            loginLog.setAddress(IPUtils.getIpAddr(HttpContextUtils.getHttpServletRequest()));
            loginLog.setPlatform(RequestUserHolder.getBaseRequest().getSysType());
            loginLog.setOpenId(dto.getOpenid());
            loginLog.setUnionId(dto.getUnionid());
            loginLog.setUserId(userBaseDO.getId());
            loginLogService.save(loginLog);
        }

        // 封装响应信息返回
        WxAuthCode2SessionResp response = new WxAuthCode2SessionResp();
        response.setCountry(wechat.getCountry());
        response.setProvince(wechat.getProvince());
        response.setCity(wechat.getCity());
        response.setUserId(userBaseDO.getId());
        response.setAvatarUrl(userBaseDO.getAvatarUrl());
        response.setSex(userBaseDO.getSex());
        response.setNickName(userBaseDO.getNickName());
        response.setPhoneNumber(userBaseDO.getPhone());
        response.setGrade(userBaseDO.getGrade());
        response.setLevel(userBaseDO.getLevel());
        response.setInviteCode(userBaseDO.getInviteCode());
        response.setWxNum(userBaseDO.getWxNum());
        response.setRegisterBindStatus(userBaseDO.getRegisterBindStatus());
        return response;
    }

    /**
     * 保存用户信息
     * 1、判断手机号是否已绑定
     * 2、判断当前账号是否已绑定手机
     * 3、通知php创建用户，数据需要php用户id
     * 4、极速版创建微信信息和用户信息
     * 5、小程序微信信息表绑定手机号和用户id
     * <p>
     * 存在一个风险，php创建用户成功后成功了，极速版创建用户失败，
     * 极速版会通知数据补偿同步旧版用户，用户可能存在短暂数据不一致问题，
     * 后续可以考虑利用mq重试机制来创建红人装用户，保证旧版成功极速版一定成功
     *
     * @param dto     手机信息
     * @param request 登录请求参数
     * @return WxGetPhoneNumberResponse 用户id
     */
    private WxGetPhoneNumberResponse saveUserInfo(WxGetPhoneNumberDTO dto, WxGetPhoneNumberRequest request) {
        // 校验手机号是否已绑定
        UserBaseDO userBaseDO = userBaseService.getOne(new LambdaUpdateWrapper<UserBaseDO>().eq(UserBaseDO::getPhone, dto.getPurePhoneNumber()));
        if (userBaseDO != null) {
            throw new BusinessException("手机号已绑定其他账号，请更换其他手机号进行授权");
        }
        // 保存微信信息
        UserCacheDTO userCache = RequestUserHolder.getUserCache();
        wechatService.update(new LambdaUpdateWrapper<UserWechatDO>().eq(UserWechatDO::getUserId, userCache.getUserId()).set(UserWechatDO::getPhone, dto.getPurePhoneNumber()));

        // 修改用户信息中的手机号信息
        userBaseService.update(new LambdaUpdateWrapper<UserBaseDO>().eq(UserBaseDO::getId, userCache.getUserId()).set(UserBaseDO::getPhone, dto.getPurePhoneNumber()));

        return BeansUtils.map(dto, WxGetPhoneNumberResponse.class);
    }

    /**
     * 更换手机号 - 发送校验原手机号验证码
     */
    public void sendVerifyOldPhoneCode() {
        // 验证码保存缓存
        String code = IdUtils.genRandomNumber(4);
        if (profiles.equals(EProfilesType.DEV.getProfiles()) || profiles.equals(EProfilesType.TEST.getProfiles())) {
            code = "1111";
        }
        UserCacheDTO userCache = RequestUserHolder.getUserCache();
        String redisKey = RedisConstant.VERIFY_PHONE_CODE + userCache.getPhone();
        stringRedisTemplate.opsForValue().set(redisKey, code, 5, TimeUnit.MINUTES);

        // 发送验证码
        if (!profiles.equals(EProfilesType.DEV.getProfiles()) && !profiles.equals(EProfilesType.TEST.getProfiles())) {
            SmsRequest smsRequest = new SmsRequest();
            smsRequest.setParams(code);
            smsRequest.setPhone(RequestUserHolder.getUserCache().getPhone());
            smsRequest.setTemplateCode(EAliSMSTemplateCode.COMMON);
            smsManagerService.sendSmsWithOptions(smsRequest);
        }
    }

    /**
     * 更换手机号 - 校验原手机号验证码
     *
     * @param request 验证码信息
     */
    public void verifyOldPhoneCode(VerifyOldPhoneCodeReq request) {
        // 校验验证码
        UserCacheDTO userCache = RequestUserHolder.getUserCache();
        String codeRedisKey = RedisConstant.VERIFY_PHONE_CODE + userCache.getPhone();
        String code = stringRedisTemplate.opsForValue().get(codeRedisKey);
        if (!request.getCode().equals(code)) {
            throw new BusinessException(EResponseCode.CAPTCHA_FAIL);
        } else {
            stringRedisTemplate.delete(codeRedisKey);
        }
    }

    /**
     * 退出登录
     *
     * @return 退出登录后的用户信息
     */
    public void logout() {
        UserCacheDTO userCacheDTO = RequestUserHolder.getUserCache();
        String redisKey = GlobalConstant.USER_INFO + userCacheDTO.getToken();
        stringRedisTemplate.delete(redisKey);
        redisKey = RedisConstant.ACCOUNT_TOKEN + userCacheDTO.getUserId();
        stringRedisTemplate.delete(redisKey);
    }

    /**
     * 发送修改手机号验证码
     *
     * @param request 用户信息
     */
    public void sendModifyPhoneCode(SendModifyPhoneNumberRequest request) {
        // 验证码保存缓存
        String code = IdUtils.genRandomNumber(4);
        if (profiles.equals(EProfilesType.DEV.getProfiles()) || profiles.equals(EProfilesType.TEST.getProfiles())) {
            code = "1111";
        }
        String redisKey = RedisConstant.MODIFY_PHONE_CODE + request.getPhoneNumber();
        stringRedisTemplate.opsForValue().set(redisKey, code, 5, TimeUnit.MINUTES);

        // 发送验证码
        if (!profiles.equals(EProfilesType.DEV.getProfiles()) && !profiles.equals(EProfilesType.TEST.getProfiles())) {
            SmsRequest smsRequest = new SmsRequest();
            smsRequest.setParams(code);
            smsRequest.setPhone(RequestUserHolder.getUserCache().getPhone());
            smsRequest.setTemplateCode(EAliSMSTemplateCode.COMMON);
            smsManagerService.sendSmsWithOptions(smsRequest);
        }
    }


    /**
     * 修改手机号
     *
     * @param request 请求参数
     */
    @Transactional(rollbackFor = Exception.class)
    public void modifyPhoneNumber(ModifyPhoneNumberRequest request) {
        // 校验验证码
        String codeRedisKey = RedisConstant.MODIFY_PHONE_CODE + request.getPhoneNumber();
        String code = stringRedisTemplate.opsForValue().get(codeRedisKey);
        if (!request.getCode().equals(code)) {
            throw new BusinessException(EResponseCode.CAPTCHA_FAIL);
        } else {
            stringRedisTemplate.delete(codeRedisKey);
        }

        // 修改用户信息中的手机号信息
        UserCacheDTO userCache = RequestUserHolder.getUserCache();
        userBaseService.update(new LambdaUpdateWrapper<UserBaseDO>().eq(UserBaseDO::getId, userCache.getUserId()).set(UserBaseDO::getPhone, request.getPhoneNumber()));

        // 缓存填充手机号、用户等级信息和邀请码
        UserCacheDTO updateUserCache = new UserCacheDTO();
        updateUserCache.setUserId(userCache.getUserId());
        updateUserCache.setPhone(request.getPhoneNumber());
        UserManager bean = SpringUtils.getBean(this.getClass());
        bean.setUserCache(updateUserCache);
    }

    /**
     * 保存微信号
     *
     * @param request
     * @return
     */
    public Result<QueryAccountResponse> saveWxNum(SaveWxNumRequest request) {
        UserCacheDTO userCache = RequestUserHolder.getUserCache();
        UserBaseDO userBaseDO = new UserBaseDO();
        userBaseDO.setId(userCache.getUserId());
        userBaseDO.setWxNum(request.getWxNum());
        userBaseService.updateById(userBaseDO);


        // 修改缓存 todo 放到MQ
        UserCacheDTO updateUserCache = new UserCacheDTO();
        updateUserCache.setUserId(RequestUserHolder.getUserCache().getUserId());
        updateUserCache.setWxNum(request.getWxNum());
        UserManager bean = SpringUtils.getBean(this.getClass());
        userCache = bean.setUserCache(updateUserCache);
        return Result.success(BeanUtil.copyProperties(userCache, QueryAccountResponse.class));
    }

    /**
     * 分页
     *
     * @param request
     * @return
     */
    public PageBean<AdminUserBasePageResponse> page(AdminUserBasePageRequest request) {
        PageHelper.startPage(request.getPageNum(), request.getPageSize());
        List<UserBaseDO> userBaseList = userBaseService.list(new LambdaQueryWrapper<UserBaseDO>().eq(UserBaseDO::getAppKey, RequestUserHolder.getBaseRequest().getAppKey()).eq(request.getStatus() != null, UserBaseDO::getStatus, request.getStatus()).eq(request.getGrade() != null, UserBaseDO::getGrade, request.getGrade()).eq(request.getLevel() != null, UserBaseDO::getLevel, request.getLevel()).eq(StringUtils.isNotBlank(request.getPhone()), UserBaseDO::getPhone, request.getPhone()).eq(StringUtils.isNotBlank(request.getNickName()), UserBaseDO::getNickName, request.getNickName()).gt(request.getStartCreateTime() != null, UserBaseDO::getCreateTime, request.getStartCreateTime()).lt(request.getEndCreateTime() != null, UserBaseDO::getCreateTime, request.getEndCreateTime()));

        // 查询余额
        List<IncomeUserBalanceDO> userBalanceList = incomeUserBalanceService.list(new LambdaQueryWrapper<IncomeUserBalanceDO>().eq(IncomeUserBalanceDO::getAppKey, RequestUserHolder.getBaseRequest().getAppKey()).eq(IncomeUserBalanceDO::getUserId, userBaseList.stream().map(UserBaseDO::getId).collect(Collectors.toSet())));
        Map<Long, IncomeUserBalanceDO> userBalanceMap = userBalanceList.stream().collect(Collectors.toMap(IncomeUserBalanceDO::getUserId, Function.identity()));


        List<AdminUserBasePageResponse> responseList = BeansUtils.mapList(userBaseList, AdminUserBasePageResponse.class);
        for (AdminUserBasePageResponse response : responseList) {
            IncomeUserBalanceDO userBalanceDO = userBalanceMap.get(response.getId());
            if (userBalanceDO != null) {
                response.setBalance(userBalanceDO.getSettle());
            }
            if (StringUtils.isNotBlank(response.getProxyCounty())) {
                response.setProxyType(3);
            } else if (StringUtils.isNotBlank(response.getProxyCounty())) {
                response.setProxyType(2);
            } else {
                response.setProxyType(1);
            }
        }
        PageBean<AdminUserBasePageResponse> pageBean = new PageBean<>();
        pageBean.setPageInfo(userBaseList);
        pageBean.setList(responseList);
        return pageBean;
    }

    /**
     * 修改用户状态
     *
     * @param request
     */
    public void updateStatus(AdminUserBaseUpdateStatusRequest request) {
        UserBaseDO userBaseDO = new UserBaseDO();
        userBaseDO.setId(request.getId());
        userBaseDO.setStatus(request.getStatus());
        userBaseService.updateById(userBaseDO);

        // 禁用踢出登录
        if (request.getStatus() == 2) {
            String userIdKey = RedisConstant.ACCOUNT_TOKEN + request.getId();
            String tokenKey = stringRedisTemplate.opsForValue().get(userIdKey);
            if (StringUtils.isNotBlank(tokenKey)) {
                stringRedisTemplate.delete(userIdKey);
                stringRedisTemplate.delete(tokenKey);
            }
        }
    }


    /**
     * 升级
     *
     * @param userBase
     * @param nextGrade
     */
    public void upgrade(UserBaseDO userBase, EUserGrade nextGrade) {
        // 变更用户等级
        UserBaseDO updateUser = new UserBaseDO();
        updateUser.setId(userBase.getId());
        updateUser.setGrade(nextGrade.getType());
        updateUser.setLevel(nextGrade.getLevel());
        userBaseService.updateById(updateUser);

        // 用户等级变更记录
        UserGradeRecordDO userGradeRecord = new UserGradeRecordDO();
        userGradeRecord.setAppKey(userBase.getAppKey());
        userGradeRecord.setUserId(userBase.getId());
        userGradeRecord.setBeforeGrade(userBase.getGrade());
        userGradeRecord.setBeforeLevel(userBase.getLevel());
        userGradeRecord.setGrade(nextGrade.getType());
        userGradeRecord.setLevel(nextGrade.getLevel());
        userGradeRecordService.save(userGradeRecord);

        // 用户关系链等级变更
        userRelationService.update(new LambdaUpdateWrapper<UserRelationDO>()
                .eq(UserRelationDO::getAppKey, userBase.getAppKey())
                .eq(UserRelationDO::getUserId, userBase.getId())
                .set(UserRelationDO::getGrade, nextGrade.getType())
                .set(UserRelationDO::getLevel, nextGrade.getLevel()));
        userRelationService.update(new LambdaUpdateWrapper<UserRelationDO>()
                .eq(UserRelationDO::getAppKey, userBase.getAppKey())
                .eq(UserRelationDO::getParentUserId, userBase.getId())
                .set(UserRelationDO::getParentGrade, nextGrade.getType())
                .set(UserRelationDO::getParentLevel, nextGrade.getLevel()));

        // 修改缓存
        UserCacheDTO updateUserCache = new UserCacheDTO();
        updateUserCache.setUserId(userBase.getId());
        updateUserCache.setGrade(nextGrade.getType());
        updateUserCache.setLevel(nextGrade.getLevel());
        UserManager bean = SpringUtils.getBean(this.getClass());
        bean.setUserCache(updateUserCache);

        // 如果是升级的话，发送升级事件
        EUserGrade userGrade = EUserGrade.getByType(userBase.getGrade(), userBase.getLevel());
        if (nextGrade.compare(userGrade) > 0) {
            // 升级事件
            UserRelationDO userRelation = userRelationService.getOne(new LambdaQueryWrapper<UserRelationDO>().eq(UserRelationDO::getAppKey, userBase.getAppKey()).eq(UserRelationDO::getUserId, userBase.getId()));
            if (userRelation != null) {
                UserGradeUpEvent userGradeUpEvent = new UserGradeUpEvent(this);
                userGradeUpEvent.setUpGrade(userGrade.getType());
                userGradeUpEvent.setUpLevel(userGrade.getLevel());
                userGradeUpEvent.setAppKey(userBase.getAppKey());
                userGradeUpEvent.setParentUserId(userRelation.getParentUserId());
                eventPublisher.publishEvent(userGradeUpEvent);
            }
        }
    }

    /**
     * 获取服务商微信号
     *
     * @return
     */
    public UserServerWxNumberGetResponse getServerWxNum() {
        Long userId = RequestUserHolder.getUserCache().getUserId();
        while (true) {
            UserRelationDO userRelation = userRelationService.getOne(new LambdaQueryWrapper<UserRelationDO>().eq(UserRelationDO::getAppKey, RequestUserHolder.getBaseRequest().getAppKey()).eq(UserRelationDO::getUserId, userId));
            if (userRelation == null) {
                return null;
            }
            if (userRelation.getParentGrade().equals(EUserGrade.SERVER_1.getType())) {
                UserBaseDO userBaseDO = userBaseService.getOne(new LambdaQueryWrapper<UserBaseDO>().eq(UserBaseDO::getAppKey, userRelation.getAppKey()).eq(UserBaseDO::getId, userRelation.getParentUserId()));
                UserServerWxNumberGetResponse response = new UserServerWxNumberGetResponse();
                response.setWxNum(userBaseDO.getWxNum());
                return response;
            }
            userId = userRelation.getParentUserId();
        }
    }

    /**
     * 获取上级信息
     *
     * @return
     */
    public UserParentInfoGetResponse getParentInfo() {
        UserParentInfoGetResponse response = new UserParentInfoGetResponse();
        Long userId = RequestUserHolder.getUserCache().getUserId();
        while (true) {
            UserRelationDO userRelation = userRelationService.getOne(new LambdaQueryWrapper<UserRelationDO>().eq(UserRelationDO::getAppKey, RequestUserHolder.getBaseRequest().getAppKey()).eq(UserRelationDO::getUserId, userId));
            if (userRelation == null) {
                break;
            }
            if (userRelation.getParentGrade().equals(EUserGrade.SERVER_1.getType())) {
                UserBaseDO userBaseDO = userBaseService.getOne(new LambdaQueryWrapper<UserBaseDO>().eq(UserBaseDO::getAppKey, userRelation.getAppKey())
                        .eq(UserBaseDO::getId, userRelation.getParentUserId()));
                UserParentInfoGetResponse.UserInfo serverUser = BeansUtils.map(userBaseDO, UserParentInfoGetResponse.UserInfo.class);
                response.setServerInfo(serverUser);
                if (response.getParentInfo() == null) {
                    response.setParentInfo(BeansUtils.map(serverUser, UserParentInfoGetResponse.UserInfo.class));
                }
                return response;
            }
            if (response.getParentInfo() == null) {
                UserBaseDO userBaseDO = userBaseService.getOne(new LambdaQueryWrapper<UserBaseDO>().eq(UserBaseDO::getAppKey, userRelation.getAppKey()).eq(UserBaseDO::getId, userRelation.getParentUserId()));
                response.setParentInfo(BeansUtils.map(userBaseDO, UserParentInfoGetResponse.UserInfo.class));
            }
            userId = userRelation.getParentUserId();
        }
        return response;
    }

    public Result<QueryAccountResponse> setRegisterBindStatus(UserRegisterAndBindStatusSetRequest request) {
        UserCacheDTO userCache = RequestUserHolder.getUserCache();
        GetPromoterRegisterAndBindStatusResp statusResp = promoterService.getPromoterRegisterAndBindStatus(userCache.getOpenId());
        if (StringUtils.isBlank(statusResp.getSharerAppid())) {
            throw new BusinessException("未完成微信机构绑定");
        }


        UserWechatDO userWechat = wechatService.getByUserId(RequestUserHolder.getAppKey(), userCache.getUserId());
        if (Objects.nonNull(userWechat)) {

            UserBaseDO userBaseDO = new UserBaseDO();
            userBaseDO.setId(userCache.getUserId());
            userBaseDO.setRegisterBindStatus(1);
            userBaseService.updateById(userBaseDO);

            String sharerAppid = statusResp.getSharerAppid();
            userWechat.setSharerAppid(sharerAppid);
            wechatService.updateById(userWechat);

            UserCacheDTO updateUserCache = new UserCacheDTO();
            updateUserCache.setUserId(RequestUserHolder.getUserCache().getUserId());
            updateUserCache.setRegisterBindStatus(request.getRegisterBindStatus());
            // 修改缓存
            UserManager bean = SpringUtils.getBean(this.getClass());
            updateUserCache = bean.setUserCache(updateUserCache);
            return Result.success(BeanUtil.copyProperties(updateUserCache, QueryAccountResponse.class));
        }
        throw new BusinessException("无效用户");
    }

    public void setRegisterBindStatusSuccess(String sharerAppid) {
        UserCacheDTO userCache = RequestUserHolder.getUserCache();
        if (Objects.equals(userCache.getRegisterBindStatus(), 0)) {
            UserBaseDO userBaseDO = new UserBaseDO();
            userBaseDO.setId(userCache.getUserId());
            userBaseDO.setRegisterBindStatus(1);
            userBaseService.updateById(userBaseDO);

            UserCacheDTO updateUserCache = new UserCacheDTO();
            updateUserCache.setUserId(RequestUserHolder.getUserCache().getUserId());
            updateUserCache.setRegisterBindStatus(1);
            // 修改缓存
            UserManager bean = SpringUtils.getBean(this.getClass());
            bean.setUserCache(updateUserCache);
        }

        UserWechatDO userWechat = wechatService.getByUserId(RequestUserHolder.getAppKey(), userCache.getUserId());
        if (Objects.nonNull(userWechat) && StringUtils.isBlank(userWechat.getSharerAppid())) {
            userWechat.setSharerAppid(sharerAppid);
            wechatService.updateById(userWechat);
        }
    }

    public Result<QueryAccountResp> setUserWechatInfo(UserWechatInfoSetReq request) {
        UserCacheDTO userCache = new UserCacheDTO();
        userCache.setUserId(RequestUserHolder.getUserCache().getUserId());
        UserWechatDO userWechatDO = new UserWechatDO();
        userWechatDO.setId(userCache.getUserId());
        if (StringUtils.isNotBlank(request.getAvatarUrl())) {
            userWechatDO.setAvatarUrl(request.getAvatarUrl());
            userCache.setAvatarUrl(request.getAvatarUrl());
        }
        if (StringUtils.isNotBlank(request.getWxName())) {
            userWechatDO.setWxName(request.getWxName());
            userCache.setUserName(request.getAvatarUrl());
        }
        wechatService.updateById(userWechatDO);

        // 修改缓存
        UserManager bean = SpringUtils.getBean(this.getClass());
        userCache = bean.setUserCache(userCache);
        return Result.success(BeanUtil.copyProperties(userCache, QueryAccountResp.class));
    }

    /**
     * 修改缓存
     *
     * @param userCache
     */
    @DistributedLock(value = "'UPDATE_USER_CACHE:' + #userCache.userId")
    public void addUserCache(UserCacheDTO userCache) {
        log.info("新增用户{}缓存，token:{}:{}", userCache.getUserId(), userCache.getToken(), JSON.toJSONString(userCache));
        String userCacheKey = GlobalConstant.USER_INFO + userCache.getToken();
        int expire = RedisConstant.ACCOUNT_TOKEN_EXPIRE_MINUTES + RedisConstant.RANDOM.nextInt(120);
        stringRedisTemplate.opsForValue().set(userCacheKey, JsonUtils.toJSONString(userCache), expire, TimeUnit.MINUTES);

        String tokenKey = RedisConstant.ACCOUNT_TOKEN + userCache.getUserId();
        stringRedisTemplate.opsForValue().set(tokenKey, userCacheKey, expire, TimeUnit.MINUTES);
    }

    /**
     * 修改缓存
     *
     * @param userCache
     */
    @DistributedLock(value = "'UPDATE_USER_CACHE:' + #userCache.userId")
    public UserCacheDTO setUserCache(UserCacheDTO userCache) {
        try {
            // 查询用户的token
            String tokenKey = RedisConstant.ACCOUNT_TOKEN + userCache.getUserId();
            String userInfoKey = stringRedisTemplate.opsForValue().get(tokenKey);
            if (StringUtils.isBlank(userInfoKey)) {
                return null;
            }
            // 查询用户信息缓存
            String userCacheJson = stringRedisTemplate.opsForValue().get(userInfoKey);
            if (StringUtils.isBlank(userCacheJson)) {
                return null;
            }
            UserCacheDTO userCacheDTO = JsonUtils.parseObject(userCacheJson, UserCacheDTO.class);
            log.info("修改用户{}缓存，token:{}:{}", userCache.getUserId(), userCacheDTO.getToken(), JSON.toJSONString(userCache));
            BeanUtil.copyProperties(userCache, userCacheDTO, CopyOptions.create().setIgnoreNullValue(true));
            int expire = RedisConstant.ACCOUNT_TOKEN_EXPIRE_MINUTES + RedisConstant.RANDOM.nextInt(120);
            stringRedisTemplate.opsForValue().set(userInfoKey, JsonUtils.toJSONString(userCacheDTO), expire, TimeUnit.MINUTES);
            return userCacheDTO;
        } catch (Exception e) {
            log.error("setUserCache error", e);
            return null;
        }
    }

    /**
     * 锁粉
     *
     * @param request
     */
    @Transactional(rollbackFor = SQLException.class)
    public void lockFans(LockFansReq request) {
        String userInviteCode = userBaseService.getObj(new LambdaQueryWrapper<UserBaseDO>()
                .select(UserBaseDO::getInviteCode)
                .eq(UserBaseDO::getAppKey, request.getAppKey())
                .eq(UserBaseDO::getId, request.getUserId()), obj -> (String) obj);
        if (StringUtils.isNotBlank(userInviteCode)) {
            if (StringUtils.isNotBlank(request.getInviteCode())) {
                throw new BusinessException("用户已被锁粉或已有下级，不可被锁粉");
            } else {
                // 走非邀请码途径不响应错误
                log.info("用户已被锁粉或已有下级，不可被锁粉");
                return;
            }
        }
        UserBaseDO parentUserBase = userBaseService.getOne(new LambdaQueryWrapper<UserBaseDO>()
                .eq(UserBaseDO::getAppKey, request.getAppKey())
                .eq(request.getParentUserId() != null, UserBaseDO::getId, request.getParentUserId())
                .eq(StringUtils.isNotBlank(request.getInviteCode()), UserBaseDO::getInviteCode, request.getInviteCode()));


        if (parentUserBase == null) {
            if (StringUtils.isNotBlank(request.getInviteCode())) {
                throw new BusinessException("邀请人不存在");
            } else {
                // 走非邀请码途径不响应错误
                log.info("邀请人不存在");
                return;
            }

        }
        if (StringUtils.isBlank(parentUserBase.getInviteCode())) {
            if (StringUtils.isNotBlank(request.getInviteCode())) {
                throw new BusinessException("邀请人不具备锁粉能力");
            } else {
                // 走非邀请码途径不响应错误
                log.info("邀请人不具备锁粉能力");
                return;
            }
        }
        // 生成邀请码,邀请码可能重复，重试5次
        String inviteCode = null;
        for (int i = 0; i < 5; i++) {
            // 获取邀请码
            inviteCode = this.getInviteCode(request.getUserId());
            UserBaseDO codeUserBase = userBaseService.getOne(new LambdaQueryWrapper<UserBaseDO>()
                    .eq(UserBaseDO::getAppKey, request.getAppKey())
                    .eq(UserBaseDO::getInviteCode, inviteCode));
            if (codeUserBase != null) {
                log.error("新增失败，重试:{}", i);
                inviteCode = null;
            }
            break;
        }
        if (inviteCode == null) {
            if (StringUtils.isNotBlank(request.getInviteCode())) {
                log.info("邀请码生成失败");
                throw new BusinessException(EResponseCode.SERVER_ERROR_CODE);
            } else {
                // 走非邀请码途径不响应错误
                log.info("邀请码生成失败");
                return;
            }
        }
        // 如果用户是普通用户，锁粉后自动升级为V1享客
        if (userCache.getGrade().equals(EUserGrade.COMMON.getType())) {
            UserBaseDO updateUser = new UserBaseDO();
            updateUser.setId(request.getUserId());
            updateUser.setGrade(EUserGrade.SHARE_1.getType());
            updateUser.setLevel(EUserGrade.SHARE_1.getLevel());
            updateUser.setInviteCode(inviteCode);
            userBaseService.updateById(updateUser);
            // 修改缓存
            UserCacheDTO updateUserCache = new UserCacheDTO();
            updateUserCache.setUserId(request.getUserId());
            updateUserCache.setGrade(EUserGrade.SHARE_1.getType());
            updateUserCache.setLevel(EUserGrade.SHARE_1.getLevel());
            updateUserCache.setInviteCode(inviteCode);
            UserManager bean = SpringUtils.getBean(this.getClass());
            bean.setUserCache(updateUserCache);
        } else {
            // 如果用户不是普通用户，只修改邀请码
            UserBaseDO updateUser = new UserBaseDO();
            updateUser.setId(userCache.getUserId());
            updateUser.setInviteCode(inviteCode);
            userBaseService.updateById(updateUser);
            // 修改缓存
            UserCacheDTO updateUserCache = new UserCacheDTO();
            updateUserCache.setUserId(request.getUserId());
            updateUserCache.setInviteCode(inviteCode);
            UserManager bean = SpringUtils.getBean(this.getClass());
            bean.setUserCache(updateUserCache);
        }
        UserBaseDO updateAfterUserBase = userBaseService.getOne(new LambdaQueryWrapper<UserBaseDO>()
                .eq(UserBaseDO::getAppKey, request.getAppKey())
                .eq(UserBaseDO::getId, request.getUserId()));
        UserRelationDO userRelationDO = new UserRelationDO();
        userRelationDO.setAppKey(request.getAppKey());
        userRelationDO.setUserId(request.getUserId());
        userRelationDO.setGrade(updateAfterUserBase.getGrade());
        userRelationDO.setLevel(updateAfterUserBase.getLevel());
        userRelationDO.setParentGrade(parentUserBase.getGrade());
        userRelationDO.setParentLevel(parentUserBase.getLevel());
        userRelationDO.setParentUserId(parentUserBase.getId());
        userRelationService.save(userRelationDO);
    }

    /**
     * 生成邀请码，豹子、连号不发
     *
     * @param userId
     * @return
     */
    private String getInviteCode(Long userId) {
        String inviteCode;
        do {
            inviteCode = IdUtils.inviteCode(userId);
            // 不是豹子或者顺子就行
        } while (StringUtils.isSequential(inviteCode) || StringUtils.isRepeated(inviteCode));
        return inviteCode;
    }

    /**
     * 发送验证码
     *
     * @param request
     */
    public void sendVerificationCode(SendVerificationCodeReq request) {
        // 1. 校验手机号是否已注册
        UserBaseDO userBase = userBaseMapper.selectOne(
                new LambdaQueryWrapper<UserBaseDO>()
                        .eq(UserBaseDO::getPhone, request.getPhone())
        );
        if (userBase == null) {
            throw new BusinessException("手机号未注册");
        }
        // 2. 生成验证码
        String code = IdUtils.genRandomNumber(6); // 生成6位验证码
        if (profiles.equals(EProfilesType.DEV.getProfiles()) || profiles.equals(EProfilesType.TEST.getProfiles())) {
            // 开发和测试环境，使用固定验证码
            code = "111111";
        }
        // 3. 保存验证码到 Redis，设置过期时间为5分钟155
        String redisKey = RedisConstant.VERIFY_PHONE_CODE + request.getPhone();
        stringRedisTemplate.opsForValue().set(redisKey, code, 50, TimeUnit.MINUTES);
        // 4. 发送验证码短信
        if (!profiles.equals(EProfilesType.DEV.getProfiles()) && !profiles.equals(EProfilesType.TEST.getProfiles())) {
            SmsRequest smsRequest = new SmsRequest();
            // 设置验证码参数
            smsRequest.setParams(code);
            // 设置手机号
            smsRequest.setPhone(request.getPhone());
            // 使用固定的验证码短信模板
            smsRequest.setTemplateCode(EAliSMSTemplateCode.COMMON);
            // 调用 SmsManager 发送短信
            smsManagerService.sendSmsWithOptions(smsRequest);
        }
    }

    /**
     * 处理阿里H5支付用户登录逻辑
     *
     * @param request
     * @return
     */
    public AliH5PayLoginResp aliH5PayLogin(AliH5PayLoginReq request) {
        // 校验验证码
        if (StringUtils.isBlank(request.getCode())) {
            throw new BusinessException("验证码不能为空");
        }
        String redisKey = RedisConstant.VERIFY_PHONE_CODE + request.getPhone();
        String storedCode = stringRedisTemplate.opsForValue().get(redisKey);
        if (!request.getCode().equals(storedCode)) {
            throw new BusinessException("验证码错误");
        }
        // 查询用户信息
        UserBaseDO userBase = userBaseMapper.selectOne(
                new LambdaQueryWrapper<UserBaseDO>()
                        .eq(UserBaseDO::getPhone, request.getPhone())
        );
        if (userBase == null) {
            throw new BusinessException("用户未注册");
        }
        // 查询用户的token
        String tokenKey = RedisConstant.ACCOUNT_TOKEN + userBase.getId();
        String userInfoKey = stringRedisTemplate.opsForValue().get(tokenKey);
        if (StringUtils.isBlank(userInfoKey)) {
            throw new BusinessException(EResponseCode.SMALL_PROGRAM_LOGIN);
        }
        // 查询用户信息缓存
        String userCacheJson = stringRedisTemplate.opsForValue().get(userInfoKey);
        if (StringUtils.isBlank(userCacheJson)) {
            throw new BusinessException(EResponseCode.SMALL_PROGRAM_LOGIN);
        }
        AliH5PayLoginResp response = new AliH5PayLoginResp();
        response.setUserInfo(userCacheJson);
        if (StringUtils.isNotBlank(redisKey)) {
            stringRedisTemplate.delete(redisKey);
        }
        return response;
    }
}
